Tidydata 심화 | groupby()

pandas
Author

강신성

Published

2023-10-26

groupby() 메소드의 심화

해당 포스트는 전북대학교 통계학과 최규빈 교수님의 강의내용을 토대로 재구성되었음을 알립니다.

1. 라이브러리 imports

import pandas as pd
import numpy as np

2. groupby

A. df.groupby


df = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
df
department gender count
0 A male 1
1 A female 2
2 B male 3
3 B female 1
g = df.groupby(by = 'department')
g
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000029243C3B250>
set(dir(g)) & {'__iter__'} # g는 반복문을 돌리기 유리하게 설계되어 있음
{'__iter__'}

g에 적용할 수 있는 메소드(dir(g))중에 '__iter__'라는 게 있다. 즉, g는 for문을 돌리려고 만든 오브젝트이다.

[i for i in g]  ## list(g)
[('A',
    department  gender  count
  0          A    male      1
  1          A  female      2),
 ('B',
    department  gender  count
  2          B    male      3
  3          B  female      1)]

두 개의 튜플이 나온다. 튜플의 원소는 그룹화하는 열과 sub_dataframe과 같은 형식이다.

dct = {k:df for k, df in g}  ## 딕셔너리 컴프리헨션
dct
{'A':   department  gender  count
 0          A    male      1
 1          A  female      2,
 'B':   department  gender  count
 2          B    male      3
 3          B  female      1}
display(dct['A'])
display(dct['B'])
department gender count
0 A male 1
1 A female 2
department gender count
2 B male 3
3 B female 1

딕셔너리의 원소가 sub-dataframe

### B. g의 이용법

- g를 이용하여 원래의 df를 복원하라.

df = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
g = df.groupby('department')
pd.concat([df for _,df in g])
department gender count
0 A male 1
1 A female 2
2 B male 3
3 B female 1

단순히 묶어주기만 해도 이렇게 나온다.

- g를 이용하여 아래와 동일한 기능을 하는 코드를 작성하라. (agg함수 사용 금지)

df = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
df.groupby('department').agg({'count':'sum'})
count
department
A 3
B 4
list(g)
[('A',
    department  gender  count
  0          A    male      1
  1          A  female      2),
 ('B',
    department  gender  count
  2          B    male      3
  3          B  female      1)]
pd.DataFrame(pd.Series({i:sum(j['count']) for i, j in g}))  ## 리스트로 먼저 묶어줘야 함.
##pd.DataFrame(pd.Series({k:df['count'].sum() for k,df in g})) 이게 더 직관적
0
A 3
B 4

- 이 데이터프레임을 class를 기준으로 그룹핑하여 sub-dataframe을 만들고, score가 높은 순서로 정렬하는 코드를 작성하라.

df = pd.DataFrame({'class':['A']*5+['B']*5, 'id':[0,1,2,3,4]*2, 'score':[60,20,40,60,90,20,30,90,95,95]})
df
class id score
0 A 0 60
1 A 1 20
2 A 2 40
3 A 3 60
4 A 4 90
5 B 0 20
6 B 1 30
7 B 2 90
8 B 3 95
9 B 4 95
g = df.groupby('class')

pd.concat([df.sort_values('score', ascending = False) for i, df in g], axis = 0).reset_index(drop = True)
class id score
0 A 4 90
1 A 0 60
2 A 3 60
3 A 2 40
4 A 1 20
5 B 3 95
6 B 4 95
7 B 2 90
8 B 1 30
9 B 0 20

3. merge

A. 가장 빈번하게 사용되는 상황


- 예시 : big 데이터프레임에 groupby+agg를 사용하여 small 데이터프레임이 생긴 경우

big = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
small = pd.DataFrame({'department':['A','B'], 'count_sum':[3,4]})
## big.groupby('department').aggregate({'count':'sum'}).rename({'count' : 'count_sum'}, axis = 1)
display("big",big)
display("small",small)
'big'
department gender count
0 A male 1
1 A female 2
2 B male 3
3 B female 1
'small'
department count_sum
0 A 3
1 B 4

department | gender | count –> big

department | count_sum –> small


department | gender | count | count_sum

이러한 작업을 하고 싶을 때, pd.merge()를 사용하면 된다.

pd.merge(big, small)
## big.merge(small)
## small.merge(big)
department gender count count_sum
0 A male 1 3
1 A female 2 3
2 B male 3 4
3 B female 1 4

사실 아래가 정확한 코드이다.

pd.merge(big, small, on = 'department')
department gender count count_sum
0 A male 1 3
1 A female 2 3
2 B male 3 4
3 B female 1 4

공통부분인 department에 따라 데이터프레임이 병합됨

  • 두 데이터프레임은 IndexLabel에 대하여 서로 다른 정보를 각각 정리한 상황
  • 두 데티어프레임에서 공통인 열(IndexLabel(을 찾고, 이것을 기준으로 데이터의 정보를 병합한다.

B. 여러가지 파라메터


# on

big = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
small = big.groupby('department').agg({'count':'sum'}).reset_index()
display("big",big)
display("small",small)
'big'
department gender count
0 A male 1
1 A female 2
2 B male 3
3 B female 1
'small'
department count
0 A 3
1 B 4

- 잘못된 코드

pd.merge(big, small)  ## department와 count가 겹친다. 이름은 겹치지만 count는 의미가 다름
department gender count

연결고리로 이해

‘A’, 1

‘A’, 2

‘B’, 3

‘B’, 1


‘A’, 3

‘B’, 4

두 데이터프레임의 연결고리가 없다. 따라서 아무것도 산출할 수 없다…

- 제대로 쓴 코드

pd.merge(big, small, on = 'department')
department gender count_x count_y
0 A male 1 3
1 A female 2 3
2 B male 3 4
3 B female 1 4

- 열의 이름을 살리면서…

pd.merge(big, small.rename({'count' : 'count_sum'}, axis = 1))  ## 공통부분을 없애줌
department gender count count_sum
0 A male 1 3
1 A female 2 3
2 B male 3 4
3 B female 1 4

어차피 이름을 바꿔야 하니, 처음부터 양식에 맞게 해주는 게 더 좋을 수 있음…

사실 아래 둘은 같은 코드이다.

pd.merge(big,small,on='department')
pd.merge(big,small,left_on='department', right_on='department')
department gender count_x count_y
0 A male 1 3
1 A female 2 3
2 B male 3 4
3 B female 1 4

# left_on, right_on

big = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
small = pd.DataFrame({'dept':['A','B'], 'count':[3,4]})
display("big",big)
display("small",small)
'big'
department gender count
0 A male 1
1 A female 2
2 B male 3
3 B female 1
'small'
dept count
0 A 3
1 B 4

공통부분은 count이고, 오히려 다른 부분은 모두 공통되지 않은 상황

pd.merge(big, small)
department gender count dept
0 B male 3 A

count로 합치면서 지랄이 난다.

- department, dept를 기준으로 병합…

pd.merge(big, small, left_on = 'department', right_on = 'dept')
## 왼쪽 데이터프레임에선 department, 오른쪽 데이터프레임에선 dept를 기준으로 삼음...
department gender count_x dept count_y
0 A male 1 A 3
1 A female 2 A 3
2 B male 3 B 4
3 B female 1 B 4

- 더 직관적이고 편하게…

pd.merge(big, small.rename({'dept' : 'department', 'count' : 'count_sum'}, axis = 1))
department gender count count_sum
0 A male 1 3
1 A female 2 3
2 B male 3 4
3 B female 1 4

강제로 한 열만 이름이 같도록 해서 공통부분을 지정해줬다.(훨씬 편하지요옹?)

# how

df1 = pd.DataFrame({
    'dept':['통계','수학','과학','IAB'], 
    'count':[20,30,25,50]
})
df2 = pd.DataFrame({
    'dept':['통계','수학','과학','신설학과'], 
    'desc':['통계학과는...','수학과는...','과학학과는...','이 학과는 내년에 신설될 예정이고...']
})
display("df1",df1)
display("df2",df2)
'df1'
dept count
0 통계 20
1 수학 30
2 과학 25
3 IAB 50
'df2'
dept desc
0 통계 통계학과는...
1 수학 수학과는...
2 과학 과학학과는...
3 신설학과 이 학과는 내년에 신설될 예정이고...

공통의 열인 dept, 서로 다른 정보인 count와 desc

on, left_on, right_on을 사용할 필요가 없다.

pd.merge(df1, df2)  ## IAB, 신설학과가 미아됨
dept count desc
0 통계 20 통계학과는...
1 수학 30 수학과는...
2 과학 25 과학학과는...

근데 겹치지 않는 것이 없어졌음…

겹치지 않는 자료를 처리하는 방식은 4가지 경우로 나누어진다.

#pd.merge(df1, df2, how = 'inner')  ## 보수적으로, 자료가 없으면 없앰, default
#pd.merge(df1, df2, how = 'left')  ## 왼쪽 거 기준으로(첫 번째)
#pd.merge(df1, df2, how = 'right')  ## 오른쪽 거 기준으로(두 번째)
pd.merge(df1, df2, how = 'outer')  ## 개방적으로, 자료를 전부 보전
dept count desc
0 통계 20.0 통계학과는...
1 수학 30.0 수학과는...
2 과학 25.0 과학학과는...
3 IAB 50.0 NaN
4 신설학과 NaN 이 학과는 내년에 신설될 예정이고...

4. concat, merge를 이용한 데이터 병합

df_course2023 = pd.DataFrame({
    'name':['최규빈']*3+['최혜미']*2+['이영미']+['양성준'],
    'year':[2023]*7,
    'course':['파이썬프로그래밍', '데이터시각화', '기계학습활용','수리통계1', '수리통계2','회귀분석1','통계수학']})
df_course2023
name year course
0 최규빈 2023 파이썬프로그래밍
1 최규빈 2023 데이터시각화
2 최규빈 2023 기계학습활용
3 최혜미 2023 수리통계1
4 최혜미 2023 수리통계2
5 이영미 2023 회귀분석1
6 양성준 2023 통계수학
df_course2024 = pd.DataFrame({
    'name':['최규빈','이영미','이영미','양성준','최혜미'],
    'year':[2024]*5,
    'course':['기계학습활용','수리통계1', '수리통계2','회귀분석1','통계수학']})
df_course2024
name year course
0 최규빈 2024 기계학습활용
1 이영미 2024 수리통계1
2 이영미 2024 수리통계2
3 양성준 2024 회귀분석1
4 최혜미 2024 통계수학
df_score = pd.DataFrame({
    'name':['최규빈','최규빈','이영미','이영미','양성준','양성준','최혜미','최혜미'],
    'year':[2023,2024]*4,
    'score':[1, 1.2, 5,5,5,5,5,5]})
df_score
name year score
0 최규빈 2023 1.0
1 최규빈 2024 1.2
2 이영미 2023 5.0
3 이영미 2024 5.0
4 양성준 2023 5.0
5 양성준 2024 5.0
6 최혜미 2023 5.0
7 최혜미 2024 5.0
df_sex = pd.DataFrame({'name':['최규빈','이영미','양성준','최혜미'],
                        'sex':['male','female','male','female']})
df_sex
name sex
0 최규빈 male
1 이영미 female
2 양성준 male
3 최혜미 female

주어진 정보를 바탕으로, 4개의 데이터프레임을 결합하라.

- 풀이

df_course2023  ## 7개 강의목록
df_course2024  ## 5개 강의목록
df_score  ## 점수
df_sex  ## 교수님 성별

위 두개는 열이 똑같다. (concat)

아래 두 개는 다른 정보이다. (merge)

pd.concat([df_course2023, df_course2024], axis = 0).reset_index(drop = True)
name year course
0 최규빈 2023 파이썬프로그래밍
1 최규빈 2023 데이터시각화
2 최규빈 2023 기계학습활용
3 최혜미 2023 수리통계1
4 최혜미 2023 수리통계2
5 이영미 2023 회귀분석1
6 양성준 2023 통계수학
7 최규빈 2024 기계학습활용
8 이영미 2024 수리통계1
9 이영미 2024 수리통계2
10 양성준 2024 회귀분석1
11 최혜미 2024 통계수학

단순히 두 개의 데이터프레임을 합쳤다.

pd.concat([df_course2023, df_course2024], axis = 0).reset_index(drop = True).merge(df_score)
name year course score
0 최규빈 2023 파이썬프로그래밍 1.0
1 최규빈 2023 데이터시각화 1.0
2 최규빈 2023 기계학습활용 1.0
3 최혜미 2023 수리통계1 5.0
4 최혜미 2023 수리통계2 5.0
5 이영미 2023 회귀분석1 5.0
6 양성준 2023 통계수학 5.0
7 최규빈 2024 기계학습활용 1.2
8 이영미 2024 수리통계1 5.0
9 이영미 2024 수리통계2 5.0
10 양성준 2024 회귀분석1 5.0
11 최혜미 2024 통계수학 5.0

연도별로 점수를 넣어줬다. name과 year만 겹치므로 알아서 합쳐진다.

pd.concat([df_course2023, df_course2024], axis = 0).reset_index(drop = True).merge(df_score).merge(df_sex)
name year course score sex
0 최규빈 2023 파이썬프로그래밍 1.0 male
1 최규빈 2023 데이터시각화 1.0 male
2 최규빈 2023 기계학습활용 1.0 male
3 최규빈 2024 기계학습활용 1.2 male
4 최혜미 2023 수리통계1 5.0 female
5 최혜미 2023 수리통계2 5.0 female
6 최혜미 2024 통계수학 5.0 female
7 이영미 2023 회귀분석1 5.0 female
8 이영미 2024 수리통계1 5.0 female
9 이영미 2024 수리통계2 5.0 female
10 양성준 2023 통계수학 5.0 male
11 양성준 2024 회귀분석1 5.0 male

name이 겹치므로 알아서 합쳐진다.